﻿using Microsoft.EntityFrameworkCore;
using Sgr.Domain.Entities.Auditing;
using Sgr.Domain.Repositories;
using Sgr.EntityFrameworkCore;
using Sgr.EventBus.RelayBoxs.Domain;
using Sgr.Exceptions;
using Sgr.Generator;
using Sgr.Utilities;

namespace Sgr.EventBus.RelayBoxs.Infrastructure.Repositories
{
    public class OutboxMessageRepository<TDbContext> :
        EfCoreRepositoryOfTEntityAndTPrimaryKey<TDbContext, OutboxMessage, long>, IOutboxMessageRepository
          where TDbContext : UnitOfWorkDbContext
    {
        private readonly ISegmentNumIdGenerator _numberIdGenerator;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="dbContextProvider"></param>
        /// <param name="auditedOperator">审计操作者</param>
        /// <param name="numberIdGenerator">数字ID生成器</param>
        /// <exception cref="BusinessException">参数为空时抛出异常</exception>
        public OutboxMessageRepository(
            IDbContextProvider<TDbContext> dbContextProvider,
            IAuditedOperator auditedOperator,
            ISegmentNumIdGenerator numberIdGenerator)
            : base(dbContextProvider, auditedOperator)
        {
            _numberIdGenerator = numberIdGenerator ?? throw new ArgumentNullException(nameof(numberIdGenerator));
        }

        /// <summary>
        /// 检查并设置主键Id
        /// </summary>
        /// <param name="entity">实体</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns></returns>
        protected override async Task CheckAndSetIdAsync(OutboxMessage entity, CancellationToken cancellationToken = default)
        {
            if (entity.Id == 0)
                entity.Id = await _numberIdGenerator.GetUniqueIdAsync(OutboxMessageProvider.TRACKER_KEY, cancellationToken);
        }

        #region IOutboxMessageRepository

        public async Task<IEnumerable<OutboxMessage>> GetPendingMessagesAsync(int limit, CancellationToken cancellationToken = default)
        {
            // 参数校验与保护
            if (limit <= 0) return Enumerable.Empty<OutboxMessage>();
            if (limit > 2000) limit = 2000;

            var db = await GetDbContextAsync(cancellationToken);
            var nowUtc = DateTime.UtcNow;

            // 使用 EF Core 查询（AsNoTracking 避免跟踪，提高性能）
            var query = db.Set<OutboxMessage>()
                          .AsNoTracking()
                          .Where(m => m.Status == OutboxStatus.Pending && m.NextOnUtc <= nowUtc)
                          .OrderBy(m => m.NextOnUtc)
                          .Take(limit);

            var list = await query.ToListAsync(cancellationToken);
            return list;
        }

        public async Task<int> CleanupDispatchedMessagesAsync(DateTime olderThanUtc, int max, CancellationToken cancellationToken = default)
        {
            if (max <= 0) return 0;
            if (max > 2000) max = 2000;

            var db = await GetDbContextAsync(cancellationToken);

            var affected = await db.Set<OutboxMessage>()
                .Where(m => m.Status == OutboxStatus.Dispatched && m.NextOnUtc < olderThanUtc)
                .OrderBy(m => m.NextOnUtc)
                .Take(max)
                .ExecuteDeleteAsync(cancellationToken);

            return affected;
        }

        public async Task<int> CleanupFailedMessagesAsync(DateTime olderThanUtc, int max, CancellationToken cancellationToken = default)
        {
            if (max <= 0) return 0;
            if (max > 2000) max = 2000;

            var db = await GetDbContextAsync(cancellationToken);

            var affected = await db.Set<OutboxMessage>()
                .Where(m => m.Status == OutboxStatus.Failed && m.NextOnUtc < olderThanUtc)
                .OrderBy(m => m.NextOnUtc)
                .Take(max)
                .ExecuteDeleteAsync(cancellationToken);

            return affected;
        }

        /// <summary>
        /// 标记为已发送
        /// </summary>
        public async Task MarkDispatchedAsync(long messageId, CancellationToken cancellationToken = default)
        {
            if (messageId <= 0) return;

            var nowUtc = DateTime.UtcNow;
            var db = await GetDbContextAsync(cancellationToken);

            await db.Set<OutboxMessage>()
                .Where(m => m.Id == messageId && m.Status != OutboxStatus.Dispatched)
                .ExecuteUpdateAsync(s => s
                    .SetProperty(m => m.DispatchedOnUtc, _ => nowUtc)
                    .SetProperty(m => m.Status, _ => OutboxStatus.Dispatched)
                    .SetProperty(m => m.NextOnUtc, _ => nowUtc),
                    cancellationToken);
        }

        /// <summary>
        /// 标记为已发送
        /// </summary>
        public async Task MarkDispatchedAsync(IEnumerable<long> messageIds, CancellationToken cancellationToken = default)
        {
            var ids = messageIds?.ToList();
            if (ids == null || ids.Count == 0) return;

            const int batchThreshold = 600; // 不同数据库参数上限不同（如 PostgreSQL 更高）。后续可考虑从配置读取或根据 DbContext.Database.ProviderName 动态调整
            var nowUtc = DateTime.UtcNow;
            var db = await GetDbContextAsync(cancellationToken);

            if (ids.Count > batchThreshold)
            {
                var batches = ids
                    .Select((id, index) => new { id, index })
                    .GroupBy(x => x.index / batchThreshold)
                    .Select(g => g.Select(x => x.id).ToList());
                foreach (var batch in batches)
                {
                    await db.Set<OutboxMessage>()
                        .Where(m => batch.Contains(m.Id) && m.Status != OutboxStatus.Dispatched)
                        .ExecuteUpdateAsync(s => s
                            .SetProperty(m => m.DispatchedOnUtc, _ => nowUtc)
                            .SetProperty(m => m.Status, _ => OutboxStatus.Dispatched)
                            .SetProperty(m => m.NextOnUtc, _ => nowUtc),
                            cancellationToken);
                }
            }
            else
            {
                await db.Set<OutboxMessage>()
                    .Where(m => ids.Contains(m.Id) && m.Status != OutboxStatus.Dispatched)
                    .ExecuteUpdateAsync(s => s
                        .SetProperty(m => m.DispatchedOnUtc, _ => nowUtc)
                        .SetProperty(m => m.Status, _ => OutboxStatus.Dispatched)
                        .SetProperty(m => m.NextOnUtc, _ => nowUtc),
                        cancellationToken);
            }
        }

        public async Task MarkFailedAsync(long messageId, string lastError = "", CancellationToken cancellationToken = default)
        {
            var db = await GetDbContextAsync(cancellationToken);
            var nowUtc = DateTime.UtcNow;
            string err = StringHelper.NormalizeAndTruncate(lastError, 2000);

            await db.Set<OutboxMessage>()
                .Where(m => m.Id == messageId && m.Status == OutboxStatus.Pending)
                .ExecuteUpdateAsync(s => s
                    .SetProperty(m => m.Status, _ => OutboxStatus.Failed)
                    .SetProperty(m => m.RetryCount, m => m.RetryCount + 1)
                    .SetProperty(m => m.LastError, _ => err)
                    .SetProperty(m => m.NextOnUtc, _ => nowUtc),
                    cancellationToken);
        }

        public async Task MarkRetryAsync(long messageId, DateTime nextOnUtc, string lastError = "", CancellationToken cancellationToken = default)
        {
            var db = await GetDbContextAsync(cancellationToken);

            string err = StringHelper.NormalizeAndTruncate(lastError, 200);

            await db.Set<OutboxMessage>()
                .Where(m => m.Id == messageId && m.Status == OutboxStatus.Pending)
                .ExecuteUpdateAsync(s => s
                    .SetProperty(m => m.RetryCount, m => m.RetryCount + 1)
                    .SetProperty(m => m.NextOnUtc, _ => nextOnUtc)
                    .SetProperty(m => m.LastError, _ => err),
                    cancellationToken);
        }

        #endregion IOutboxMessageRepository
    }
}